﻿using System.Collections.Generic;
using System.Text;
using System.IO;
using Newtonsoft.Json;
using System.Text.RegularExpressions;
using System;

namespace ValidationFramework.Web.MVC
{
	/// <summary>
	/// Handles the generation of JQuery compliant javascript.
	/// </summary>
	/// <remarks>
	/// For the creation of html client side validation.
	/// </remarks>
	public class JQueryGenerator : IClientValidationGenerator
	{
		// --- Fields
		private bool _advancedMode;

		// --- Constructors
		/// <summary>
		/// Initializes a new instance of the JQueryGenerator class.
		/// </summary>
		public JQueryGenerator()
		{
			_advancedMode = false;
		}

		/// <summary>
		/// Initializes a new instance of the JQueryGenerator class.
		/// </summary>
		/// <param name="advancedMode">If true the generator will create scripts that add rules to JQuery's Validate Plugin
		/// but leaves it up to the consumer to setup/call the JQuery.validate(options) function.</param>
		public JQueryGenerator(bool advancedMode)
		{
			_advancedMode = advancedMode;
		}


		// --- Methods | Simple Mode Generation
		internal string GenerateRuleOptions(string formId, ElementRuleDictionary elementRuleDictionary)
		{
			const string optionTemplate = @"
$(""#%FormId%"").validate({
errorContainer: $(""validation-summary-message""),
errorLabelContainer: ""ul.validation-summary-errors"",
errorClass:""input-validation-error"",
wrapper: ""li"",
%RulesAndMessages%
});";

			var stringBuilder = new StringBuilder();
			using (var stringWriter = new StringWriter(stringBuilder))
			{
				using (JsonWriter writer = new JsonTextWriter(stringWriter))
				{
					writer.Formatting = Formatting.Indented;
					writer.WriteRaw("rules:");
					WriteRules(writer, elementRuleDictionary);
					// json.net isn't separating these 2 properties, do it by hand
					writer.WriteRaw(",\n");
					writer.WriteRaw("messages:");
					WriteMessages(writer, elementRuleDictionary);
				}
			}


			var script = optionTemplate;
			script = Regex.Replace(script, "%FormId%", formId);
			script = Regex.Replace(script, "%RulesAndMessages%", stringBuilder.ToString());
			return WrapWithDocReadyFunction(script);
		}
		// writing message options
		internal void WriteMessages(JsonWriter writer, ElementRuleDictionary formRules)
		{
			writer.WriteStartObject();
			foreach (var item in formRules)
			{
				WriteElementRuleMessages(writer, item.Key, item.Value);
			}
			writer.WriteEndObject();
		}
		internal void WriteElementRuleMessages(JsonWriter writer, string elementId, IList<IClientRule> elementRules)
		{
			writer.WritePropertyName(elementId);
			writer.WriteStartObject();
			foreach (var clientRule in elementRules)
			{
				writer.WritePropertyName(clientRule.ClientFunctionCall.FunctionName);
				writer.WriteValue(clientRule.ErrorMessage);
			}
			writer.WriteEndObject();
		}
		// writing rules options
		internal void WriteRules(JsonWriter writer, ElementRuleDictionary formRules)
		{

			writer.WriteStartObject();
			foreach (var item in formRules)
			{
				WriteElementRules(writer, item.Key, item.Value);
			}
			writer.WriteEndObject();
		}
		internal void WriteElementRules(JsonWriter writer, string elementId, IList<IClientRule> elementRules)
		{
			writer.WritePropertyName(elementId);
			writer.WriteStartObject();
			foreach (var clientRule in elementRules)
			{
				WriteElementRule(writer, clientRule.ClientFunctionCall);
			}
			writer.WriteEndObject();
		}
		internal void WriteElementRule(JsonWriter writer, ClientFunctionCall rule)
		{
			writer.WritePropertyName(rule.FunctionName);
			var originalFormatting = writer.Formatting;
			writer.Formatting = Formatting.None;

			if (rule.FunctionArgs != null)
			{
				writer.WriteStartArray();
				foreach (var argument in rule.FunctionArgs)
				{
					writer.WriteValue(argument);
				}
				writer.WriteEndArray();
			}
			writer.Formatting = originalFormatting;
		}


		// --- Methods | Advanced Mode Generation
		internal string GenerateRules(ElementRuleDictionary elementRuleDictionary)
		{
			var scriptBuilder = new StringBuilder();

			string ruleTemplate = @"$(""#%elementID%"").rules(""add"", %rules%);";
			string rules = string.Empty;
			foreach (var kvp in elementRuleDictionary)
			{
				var ruleBuilder = new StringBuilder();
				var ruleString = ruleTemplate;
				string elementID = kvp.Key;


				// build rules JSON
				using (var ruleWriter = new StringWriter(ruleBuilder))
				{

					using (JsonWriter jsonWriter = new JsonTextWriter(ruleWriter))
					{
						jsonWriter.Formatting = Formatting.Indented;
						IList<IClientRule> elementRules = kvp.Value;

						jsonWriter.WriteStartObject(); // start rule list
						foreach (var rule in elementRules)// write all rules
						{
							jsonWriter.WritePropertyName(rule.ClientFunctionCall.FunctionName);
							jsonWriter.WriteStartArray();
							foreach (var argument in rule.ClientFunctionCall.FunctionArgs)
							{
								jsonWriter.WriteValue(argument);
							}
							jsonWriter.WriteEndArray();
						}

						jsonWriter.WritePropertyName("messages");// add message property
						jsonWriter.WriteStartObject();// start message list
						foreach (var clientRule in elementRules)
						{
							jsonWriter.WritePropertyName(clientRule.ClientFunctionCall.FunctionName);
							jsonWriter.WriteValue(clientRule.ErrorMessage);
						}
						jsonWriter.WriteEndObject();// end message list
						jsonWriter.WriteEndObject();// end rule list
					}

					ruleString = Regex.Replace(ruleString, "%elementID%", elementID);
					ruleString = Regex.Replace(ruleString, "%rules%", ruleBuilder.ToString());
				}
				scriptBuilder.AppendLine(ruleString);
			}

			return WrapWithDocReadyFunction(scriptBuilder.ToString());
		}


		// --- Methods | Helpers
		private string WrapWithDocReadyFunction(string strToWrap)
		{
			const string docReadyTemplate = @"
// Generated by the .Net Validation Framework
$(document).ready(function() {
   
%script%

});";
			var result = docReadyTemplate;
			result = Regex.Replace(docReadyTemplate, "%script%", strToWrap);
			return result;
		}
		

		// --- IClientRuleGenerator Members
		public string Generate(string formId, ElementRuleDictionary elementRuleDictionary)
		{
			if (elementRuleDictionary == null || elementRuleDictionary.Count <= 0)
				return string.Empty;

			if (_advancedMode)
				return GenerateRules(elementRuleDictionary);
			else
			{
				return GenerateRuleOptions(formId, elementRuleDictionary);
			}

		}
	}
}
